#include "OBJLoader.h"

#include <fstream>
#include <vector>



const D3D10_INPUT_ELEMENT_DESC layout_OBJLoader[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
	{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 36, D3D10_INPUT_PER_VERTEX_DATA, 0 },
} ;
UINT layout_num_elements = sizeof( layout_OBJLoader ) / sizeof( layout_OBJLoader[0] );



OBJLoader::OBJLoader() :	positions(0), normals(0), uvs(0), vertices(0), indices(0), attributes(0), materials(0), mtl_file(""), g_device(0), 
							m_NumAttribTableEntries(0), position_count(0), normal_count(0), uv_count(0), index_count(0), vertex_count(0), tri_tangents(0), 
							strName(L""), material_count(0), face_count(0), path_media("")
{

}



OBJLoader::~OBJLoader()
{
	try 
	{
		if (g_device) g_device->Release();

		for (int i=0; i<material_count; i++)
		{
			if (materials[i].pTextureRV10 != 0) {
				materials[i].pTextureRV10->Release();
				materials[i].pTextureRV10 = 0;
			}
	
			if (materials[i].pTextureNormalPRV10 != 0) {
				materials[i].pTextureNormalPRV10->Release();
				materials[i].pTextureNormalPRV10 = 0;
			}

			if (materials[i].pTextureSpecularPRV10 != 0) {
				materials[i].pTextureSpecularPRV10->Release();
				materials[i].pTextureSpecularPRV10 = 0;
			}
		}

		delete[] materials;
		materials=0;
		
		if (positions) { delete[] positions; positions=0; }
		if (normals) { delete[] normals; normals=0; }
		if (uvs) { delete[] uvs; uvs=0; }
		if (vertices) { delete[] vertices; vertices=0; }
		if (indices) { delete[] indices; indices=0; }
		if (attributes) { delete[] attributes; attributes=0; }
		if (tri_tangents) { delete[] tri_tangents; tri_tangents=0; }

	}
	catch(...)
	{
		MessageBoxA(0, "Exception thrown in OBJLoader destructor", 0, 0);
	}
}


void OBJLoader::load(	ID3D11Device*	g_device,
						const char*		filename, 
						ID3D11Buffer**				vertex_buffer, 
						ID3D11Buffer**				index_buffer, 
						Material&					_material, 
						UINT&						n_vertices, 
						UINT&						n_indices, 
						ID3D11ShaderResourceView**	srv)
{

	this->g_device = g_device;
	g_device->AddRef();

	// make sure the folder path is set
	if (path_media == "")
		MessageBoxA(0, "Warning: folder path not set!", 0, 0);

	std::ifstream file(path_media + filename);
	if (!file)
	{
		std::string message("OBJLoader file error: ");
		message = message.append(filename);
		MessageBoxA(0, message.c_str(), 0, 0);
	}
	
	firstPass(file);


	positions  = new XMFLOAT3[position_count];
	normals    = new XMFLOAT3[normal_count];
	uvs        = new XMFLOAT2[uv_count];
	
	vertices   = new Vertex[face_count*3];
	indices    = new DWORD[face_count*3];
	attributes = new DWORD[face_count];

	tri_tangents = new XMFLOAT3[face_count]; // one tangent for each triangle


	materialPass();

	secondPass(file);

	file.close();
	
	computeVertexTangent();


	_material = materials[0];
	if (_material.pTextureNormalPRV10)
		_material.pTextureNormalPRV10->AddRef();
	if (_material.pTextureRV10)
		_material.pTextureRV10->AddRef();
	if (_material.pTextureSpecularPRV10)
		_material.pTextureSpecularPRV10->AddRef();


	// Create Vertex Buffer
	D3D11_BUFFER_DESC vb_desc;
	vb_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	if (srv != NULL)
		vb_desc.BindFlags |= D3D11_BIND_SHADER_RESOURCE;

	vb_desc.ByteWidth = sizeof(Vertex) * vertex_count;
	vb_desc.CPUAccessFlags = 0;
	vb_desc.MiscFlags = 0;
	vb_desc.StructureByteStride = 0;
	vb_desc.Usage = D3D11_USAGE_DEFAULT;

	D3D11_SUBRESOURCE_DATA vb_data;
	vb_data.pSysMem = vertices;
	vb_data.SysMemPitch = vb_data.SysMemSlicePitch = 0;

	HRESULT hr = g_device->CreateBuffer(&vb_desc, &vb_data, vertex_buffer);
	if (FAILED(hr))
		MessageBoxA(0, "Error creating Vertex Buffer", 0, 0);

	if (srv != NULL) {
		D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
		ZeroMemory(&srv_desc, sizeof(srv_desc));	
		g_device->CreateShaderResourceView(*vertex_buffer, &srv_desc, srv);
	}

	// Create Index Buffer
	D3D11_BUFFER_DESC ib_desc;
	ib_desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	ib_desc.ByteWidth = sizeof(DWORD) * face_count * 3;
	ib_desc.CPUAccessFlags = ib_desc.MiscFlags = ib_desc.StructureByteStride = 0;
	ib_desc.Usage = D3D11_USAGE_DEFAULT;

	D3D11_SUBRESOURCE_DATA id_data;
	id_data.pSysMem = indices;
	id_data.SysMemPitch = id_data.SysMemSlicePitch = 0;

	hr = g_device->CreateBuffer(&ib_desc, &id_data, index_buffer);
	if (FAILED(hr))
		MessageBoxA(0, "Error creating Vertex Buffer", 0, 0);


	n_vertices = vertex_count;
	n_indices  = face_count * 3;

	
}

bool OBJLoader::materialPass()
{

	std::wifstream mat_file;

	if (mtl_file != "")
	{
		string mtl_file_path = path_media + mtl_file;
		mat_file.open(mtl_file_path.c_str());

		if (!mat_file) {
			std::string err = "OBJLoader : Error opening material file";
			err = err.append(mtl_file_path);
			MessageBoxA(0, err.c_str(), 0, 0);
			return false;
		}	
	}

	
	

	std::wstring token, mat_name;

	material_count = 0;

	for(;;)
	{
		mat_file >> token;
		if (!mat_file) break;

		if (0 == token.compare(L"newmtl"))
			material_count++;
	}

	materials = new Material[material_count];

	mat_file.clear();
	mat_file.seekg(0, std::ios::beg);

	material_count = -1;

	Material mat;

	float r,g,b;

	for(;;)
	{
		mat_file >> token;
		if (!mat_file) break;

		if (0 == token.compare(L"newmtl"))
		{
			material_count++;
			mat_file >> materials[material_count].strName;
		}
		else if (0 == token.compare(L"Ns"))
			mat_file >> materials[material_count].vSpecular.w;

		else if (0 == token.compare(L"Ka")) {
			mat_file >> r;
			mat_file >> g;
			mat_file >> b;
			materials[material_count].vAmbient = XMFLOAT4(r, g, b, 1.0f);
		}
		else if (0 == token.compare(L"Kd")) {
			mat_file >> r;
			mat_file >> g;
			mat_file >> b;
			materials[material_count].vDiffuse.x = r;
			materials[material_count].vDiffuse.y = g;
			materials[material_count].vDiffuse.z = b;
		}
		else if (0 == token.compare(L"Ks")) {
			mat_file >> r;
			mat_file >> g;
			mat_file >> b;
			materials[material_count].vSpecular.x = r;
			materials[material_count].vSpecular.y = g;
			materials[material_count].vSpecular.z = b;
		}
		else if (0 == token.compare(L"d")) {
			mat_file >> materials[material_count].vDiffuse.w; 
		}
		
		else if (0 == token.compare(L"map_Kd"))
			mat_file >> materials[material_count].strTexture;
		else if (0 == token.compare(L"map_bump"))
			mat_file >> materials[material_count].strTextureNormal;
		else if (0 == token.compare(L"map_Ks"))
			mat_file >> materials[material_count].strTextureSpecular;
			
	}
	
	mat_file.close();

	material_count++;

	HRESULT hres;
	std::wstring tex_str;

	// Load textures
	for (int i=0; i < material_count; i++)
	{
		materials[i].pTextureRV10 = 0;
		
		if (0 != materials[i].strTexture.compare(L""))
		{
			materials[i].vIsTextured.x = 1.0f;
			tex_str = std::wstring(path_media.begin(), path_media.end()) + materials[i].strTexture;
			hres = D3DX11CreateShaderResourceViewFromFile( g_device, tex_str.c_str(), 0, 0, &(materials[i].pTextureRV10), 0);
			if (hres != S_OK) {
				MessageBoxA(0, "OBJLoader: Error loading texture", 0, 0);
				MessageBox(0, tex_str.c_str(), 0, 0);
				return false;
			}

		}

		materials[i].pTextureNormalPRV10 = 0;

		if (0 != materials[i].strTextureNormal.compare(L""))
		{
			materials[i].vIsTextured.z = 1.0f;
			tex_str = std::wstring(path_media.begin(), path_media.end()) + materials[i].strTextureNormal;
			hres = D3DX11CreateShaderResourceViewFromFile( g_device, tex_str.c_str(), 0, 0, &(materials[i].pTextureNormalPRV10), 0);
			if (hres != S_OK)
				MessageBoxA(0, "OBJLoader: Error loading texture", 0, 0);
		}

		materials[i].pTextureSpecularPRV10 = 0;

		if (0 != materials[i].strTextureSpecular.compare(L""))
		{
			materials[i].vIsTextured.y = 1.0f;
			tex_str = std::wstring(path_media.begin(), path_media.end()) + materials[i].strTextureSpecular;
			hres = D3DX11CreateShaderResourceViewFromFile( g_device, tex_str.c_str(), 0, 0, &(materials[i].pTextureSpecularPRV10), 0);
			if (hres != S_OK)
				MessageBoxA(0, "OBJLoader: Error loading texture", 0, 0);
		}
	}

	return true;
}

bool OBJLoader::secondPass(std::ifstream& file)
{

	// return to the beginning of the file
	file.clear();
	file.seekg(0, std::ios::beg);

	std::string token, mat_name;

	position_count = normal_count = uv_count = index_count = vertex_count = face_count = 0;
	int pos_index, nor_index, uv_index, current_material;

	XMFLOAT3 pos, normal;
	XMFLOAT2 tex;

	Vertex v;

	for(;;)
	{

		file >> token;

		if (!file) break;

		if (token == "v")
		{
			file >> pos.x; 
			file >> pos.y;
			file >> pos.z;

			positions[position_count] = pos;
			position_count++;
		}
		else if (token == "vn")
		{
			file >> normal.x;
			file >> normal.y;
			file >> normal.z;
			normals[normal_count] = normal;
			normal_count++;
		}
		else if (token == "vt")
		{
			file >> tex.x;
			file >> tex.y;
			tex.y = 1.0f - tex.y;
			uvs[uv_count] = tex;
			uv_count++;
		}
		else if (token == "usemtl")
		{
			file >> mat_name;
			std::wstring wstr = std::wstring(mat_name.begin(), mat_name.end());
	
			for (int i=0; i<material_count; i++)
			{
				if (0 == materials[i].strName.compare(wstr)) {
					current_material = i;
					break;
				}
			}
		}
		else if (token == "f")
		{
			for (int i=0; i<3; i++)
			{
				uv_index = nor_index = uv_index = 0;

				ZeroMemory(&v, sizeof(Vertex));

				file >> pos_index;
			
				if (file.peek() == '/')
				{
					file.ignore();

					if (file.peek() != '/')
						file >> uv_index;

					if (file.peek() == '/') {
						file.ignore();
						file >> nor_index;
					}
				}

				v.position = positions[pos_index - 1];
				v.normal   = normals[nor_index - 1];
				v.tex      = uvs[uv_index - 1];

				bool bExists = false;

				for (UINT j=0; j<vertex_count; j++)
				{
					//if (v.position == vertices[j].position &&  v.normal   == vertices[j].normal   && v.tex      == vertices[j].tex)
					if (memcmp((void*)&v, (void*)&vertices[j], sizeof(Vertex)) == 0)
					{
							indices[index_count] = j;
							index_count++;

							bExists = true;
							break;
					}
				}

				//////

				if (!bExists)
				{
					vertices[vertex_count] = v;
					indices[index_count] = vertex_count;

					vertex_count++;
					index_count++;
				}
			}

			attributes[face_count] = current_material;
			face_count++;

			computeTriTangent();
		}
	}

	return true;
}


void OBJLoader::computeTriTangent()
{
	Vertex p0, p1, p2;
	XMFLOAT3 T;

	if (face_count - 1 == 150)
	{
		int a = 2;
	}

	// indices for the last triangle are : index_count - 1, index_count - 2, index_count - 3
	p0 = vertices[indices[index_count-1]];
	p1 = vertices[indices[index_count-2]];
	p2 = vertices[indices[index_count-3]];

	XMFLOAT3 Q1, Q2;

	// Q1 = p1 - p0								// Q2 = p2 - p0
	Q1.x = p1.position.x - p0.position.x; 		Q2.x = p2.position.x - p0.position.x;	
	Q1.y = p1.position.y - p0.position.y;		Q2.y = p2.position.y - p0.position.y;
	Q1.z = p1.position.z - p0.position.z;		Q2.z = p2.position.z - p0.position.z;

	float u1, u2, v1, v2;

	u1 = p1.tex.x - p0.tex.x;		v1 = p1.tex.y - p0.tex.y;
	u2 = p2.tex.x - p0.tex.x;		v2 = p2.tex.y - p0.tex.y;


	float d = ((u1 * v2) - (u2 * v1));
	
	d = 1.0f / d;

	T.x = d * (v2 * Q1.x - v1 * Q2.x);	
	T.y = d * (v2 * Q1.y - v1 * Q2.y);
	T.z = d * (v2 * Q1.z - v1 * Q2.z);

	// normalize T
	float length = T.x*T.x + T.y*T.y + T.z*T.z;
	length = sqrt(length);

	T.x /= length;
	T.y /= length;
	T.z /= length;

	// save T
	tri_tangents[face_count-1] = T;
}

void OBJLoader::computeVertexTangent()
{
	
	
	std::vector<int> tri_indices;
	
	// for each vertex..
	for (unsigned int i=0; i<vertex_count; i++)
	{

		tri_indices.clear();

		// for each triangle..
		for (unsigned int j=0; j<face_count; j++)
		{
			// indices of the current triangle
			int index0 = indices[3*j+0];
			int index1 = indices[3*j+1];
			int index2 = indices[3*j+2];

			// check if current (ith) vertex participates in triangle
			if (index0 == i || index1 == i || index2 == i)
				tri_indices.push_back(j);
		}

		// sum
		XMFLOAT3 T(0.0f, 0.0f, 0.0f);
		for (unsigned int j=0; j<tri_indices.size(); j++) {
			T.x += tri_tangents[tri_indices[j]].x;
			T.y += tri_tangents[tri_indices[j]].y;
			T.z += tri_tangents[tri_indices[j]].z;
		}
		// average
		T.x /= (float)tri_indices.size();
		T.y /= (float)tri_indices.size();
		T.z /= (float)tri_indices.size();

		// save
		vertices[i].tangent = T;
	}
}


bool OBJLoader::firstPass(std::ifstream& file)
{
	
	position_count = normal_count = uv_count = material_count = face_count = 0;
	mtl_file = "";

	std::string token, mt_name;

	for(;;)
	{
		file >> token;

		if (!file) break;

		if (token == "v") 
			position_count++;
		else if (token == "vn")
			normal_count++;
		else if (token == "vt")
			uv_count++;
		else if (token == "f")
			face_count++;
		else if (token == "mtllib")
			file >> mtl_file;
	}


	return true;
}
